Seil Simulation
In diesem Tutorial werden wir die Simulation eines Seils behandeln. Diese Simulation basiert auf der einfachen physikalischen Simulations Engine aus Lektion 39. Um etwas aus diesem Tutorial lernen zu können, sollten Sie wissen, wie Kräft auf Massen in Simulationen angewendet werden, wie Position und Geschwindigkeit einer Masse während eines Simulationslaufs iteriert werden und wie 3D Vektor-Operationen in der Physik verwendet werden. Wenn Sie bei einem dieser Themen Zweifel haben, lesen Sie über diese noch mal in Lektion 39 und anderen Quellen nach und entwickeln Sie ein paar Applikationen.
In physikalischen Simulationen möchte man erreichen, physikalische Eigenschaften nachzubilden, die sich genauso verhalten wie in ein natürlichen Umgebung. Bewegungen in Simulationen können nicht immer exakt genauso sein wie in der Natur. Ein Modell, dass die Bewegung darstellt, die wir simulieren wollen, muss so gestaltet werden, dass sie die physikalischen Eigenschaften berücksichtigt. Das Modell, welches wir erzeugen, muss enthalten wie präzise und detailiert die Bewegung von der Simulation dargestellt werden soll. Wollen wir die Atome, Elektronen oder die Photonen darstellen und beachten oder wollen wir nur die ungefähre Bewegung eines Clusters von Partikeln darstellen? Was ist die Skalierung, die wir sehen wollen? Was ist die Skalierung für Platz und Zeit?
Die Skalierung für den Platz und die Zeit, die beobachtet werden soll, hängt ab von:
1. der mathematischen Berechnung der Bewegung
2. der Performance des Computers den wir für die Simulation verwenden
1. Mathematische Berechnung von Bewegungen:
Hier wird die Mathematik der Bewegung 'klassische Mechanik' genannt, was eine einfache Repräsentation von Massen als Partikel im Raum ist und die Beschleunigung dieser Massen durch Kräfte, während die Zeit voran schreitet. In der Skalierung, die wir mit blossem Auge sehen können, ist die klassische Mechanik durchaus zu gebrauchen. Deshalb können wir die klassische Mechanik dazu verwenden, Objekte und Mechanismen aus unserem täglichen Leben zu simulieren. In Lektion 39 wurde die Kraft der Gravitation und der Feder auf Massen von 1 Kg angewandt, indem die klassische Mechanik verwendet wurde. In diesem Tutorial werden wir die klassische Mechanik in einer Seil-Simulation verwenden.
2. Performance des Computers den wir für die Simulation verwenden:
Die Performance des Computers, auf dem die Simulation läuft, bestimmt, wie detailliert wir alles darstellen und beobachten können. Wenn wir zum Beispiel einen laufenden Menschen auf einem langsamen Rechner simulieren, würden wir darüber nachdenken, die Simulation der Zehen wegzulassen. Die Zehen haben sicherlich eine wichtige Rolle. Doch selbst ohne die Zehen in der Simulation könnten wir einen laufenden Menschen erhalten. Vielleicht wäre die Qualität der Bewegung recht niedrig, aber die Berechnungen würden weniger ins Gewicht fallen. In dem Beispiel des laufenden Menschen, zwingt uns die Performance des Computers als Massstab die Füße und Beide zu nehmen und die Zehen wegzulassen.
Die physikalischen Eigenschaften des Seils entwerfen:
Unter der Vorraussetzung, dass wir die klassische Mechanik haben (für die mathematische Berechnung der Bewegung) und einen Computer mit 500 MHz CPU Geschwindigkeit (wir wählen das als minimale Anforderung), werden wir die physikalische Eigenschaften einer Seil-Simulation entwerfen. Als erstes müssen wir bestimmen, wieviele Details wir haben wollen. Während wir den Code implementieren, werden wir Physics1.h aus Lektion 39 verwenden. In Physics1.h haben wir eine "class Mass", welche eine Masse als einen Punkt-Partikel repräsentiert. Wir können die Mass Klasse verwenden. Wenn wir diese Punkt-ähnlichen Massen mit Federn untereinander verbinden, können wir ein physikalisches Modell formen, um ein Seil darzustellen. Von dem Modell ausgehend können wir bestimmen, wie detailliert die Bewegung des Seils sein wird. Wenn wir diese Punktartigen Massen mit Federn untereinander verbinden, können wir ein physikalisches Modell formen, dass ein Seil repräsentiert. Aus dem Modell heraus können wir bestimmen, wie detailiert die Bewegung des Seils sein würde. Wir können daraus herleiten, dass das Seil Schwingende und Wellen-Bewegungen aufzeigen wird, aber keine verdrehende (swirling) Bewegung. (Stellen Sie sich dieses verdrehen so vor: sagen wir, Sie haben ein dünnes Seil zwischen zwei Fingern und Sie reiben jetzt Ihre Finger, so dass das Seil sich verdreht). Wir können das Verdrehen nicht beobachten, da wir punkt-artige Partikel in dem Modell verwenden. Punkt-artige Partikel können sich nicht um eine Achse drehen, weshalb das Seil nicht verdreht wird. Wir entscheiden uns, dass oben beschriebene Modell zu verwenden und halten fest, dass unsere Details sich auf das Schwingen und Wellen-Bewegung beschränken. Halten wir ebenso fest, dass wir die Wellen-Bewegung des Seil's im 10 cm Detail beobachten wollen. Das bedeutet, dass das Seil Diskontinuitäten unter 10 cm aufweist. Ich habe diese Einschräkung vorgenommen, da ich 50 oder 100 Partikel im Seil benutzen möchte (wegen der Performance) und ich das Seil 3 oder 4 Meter lang haben möchte. Was bedeutet, dass 3 bis 8 cm zwischen den Partikeln des Seils sind, was unter dem Diskontinuitäts-Level ist, welches wir gewählt haben (10 cm).
Die Bewegungsgleichung bestimmen:
Die Bewegungsgleichung bezeichnet im mathematischem Sinne eine Differnzial-Gleichung zweiten Grades und meint damit eigentlich, dass Kräfte in einer physikalischen Umgebung wirken. Nehmen wir die letztere Bedeutung, da sie sich beser anhört. Die Bewegungsgleichung bestimme bedeutet, die Kräfte zu bestimmen. In dem Seil-Modell werden die Kräfte auf die Partikel wirken, aus denen das Seil besteht. Die erste Kraft wird die Seil-Spannung zwischen diesen Partikeln sein. Unten wir jeder Partikel mit einem "O" gekennzeichnet und die Federn mit "----":
O----O----O----O 1 2 3 4
class Spring // Ein Objekt um eine Feder mit innerer Reibung zu repräsentieren, welche zwei Massen verbindet. Die Feder
{ // hat eine normalisierte Länge (Die Länge, wo auf die Feder keine Kräfte angewandt werden)
public:
Mass* mass1; // Die erste Masse an einem Ende der Feder
Mass* mass2; // Die zweite Masse am anderen Ende der Feder
float springConstant; // Eine Konstante, um die Steifheit der Feder zu repräsentieren
float springLength; // Die Länge, wo auf die Feder keine Kraft angewandt wird
float frictionConstant; // Eine Konstante, die für die innere Reibung der Feder verwendet wird
Spring(Mass* mass1, Mass* mass2,
// Konstruktor
float springConstant, float springLength, float frictionConstant)
{
this->springConstant = springConstant; // Setze springConstant
this->springLength = springLength; // Setze die Federlänge (springLength)
this->frictionConstant = frictionConstant; // Setze die frictionConstant (Reibungskonstante)
this->mass1 = mass1; // Setze mass1
this->mass2 = mass2; // Setze mass2
}
void solve() // solve() Methode: Die Methode wor die Kräfte angewandt werden
{
Vector3D springVector = mass1->pos - mass2->pos; // Vektor zwischen den zwei Massen
float r = springVector.length(); // Abstand zwischen zwei Massen
Vector3D force; // Force (Kraft) hat anfangs einen Wert von Null
if (r != 0) // Um eine Division durch Null zu vermeiden... überprüfe, ob r gleich null ist
// Die Federkraft wird zur Kraft (force) addiert
force += -(springVector / r) * (r - springLength) * springConstant;
...
(void solve() continued) force += -(mass1->vel - mass2->vel) * frictionConstant; // Die Reibungskraft wird zu force addiert // mit dieser Addition erhalten wir die Netzkraft der Feder mass1->applyForce(force); // Force wird auf mass1 angewandt mass2->applyForce(-force); // Die Negation von Force wird auf mass2 angewandt } // Void Solve() Endet hier
| 1. virtual void init() | -> | Resettet die Kräfte. |
| 2. virtual void solve() | -> | Die gewünschten Kräfte werden angewendet. |
| 3. virtual void simulate(float dt) | -> | Position und Geschwindigkeit werden iteriert. |
| 4. virtual void operate(float dt) | -> | Methode 1., 2., und 3. werden in eine gepackt, so dass sie alle aufgerufen werden. |
class RopeSimulation : public Simulation // Ein Objekt um ein Seil zu simulieren, dass mit einer glatten Oberfläche und der Luft interagiert
{
public:
Spring** springs; // Federn verbinden die Massen (davon sollte es [numOfMasses - 1] geben)
Vector3D gravitation; // Gravitationsbeschleunigung (Gravitation wird auf alle Massen angewandt)
Vector3D ropeConnectionPos; // Ein Punkt im Raum, der dazu verwendet wird, um die erste Position der
// ersten Masse im System (Masse mit dem Index 0) zu setzen
Vector3D ropeConnectionVel; // Eine Variable um ropeConnectionPos zu bewegen (dadurch können wir das Seil schwingen)
float groundRepulsionConstant; // Eine Konstante die repräsentiert wieviel die Massen vom Boden abprallen
float groundFrictionConstant; // Eine Konstante für die Reibung, die auf die Massen am Boden angewandt werden
// (wird für's Schlittern des Seiles auf dem Boden verwendet)
float groundAbsorptionConstant; // Eine Konstante für die Reibungs-Absorption, die auf die Massen auf dem Boden angewandt wird
// (wird für vertikale Kollisionen des Seils mit dem Boden verwendet)
float groundHeight; // Ein Wert der den Y-Wert auf dem Boden repräsentiert
// (Der Boden ist eine glatte Oberfläche in die +Y Richtung zeigt)
float airFrictionConstant; // Eine Konstante für die Luftreibung, die auf die Massen angewandt werden
RopeSimulation( // Ein langer langerKonstruktur mit 11 Parameter fängt hier an
int numOfMasses, // 1. Die Anzahl der Massen
float m, // 2. Gewicht jeder Masse
float springConstant, // 3. Wie steif die Federn sind
float springLength, // 4. Die Länge, wo eine Feder keiner Kraft ausgesetzt ist
float springFrictionConstant, // 5. Innere Reibungskonstante der Feder
Vector3D gravitation, // 6. Gravitationsbeschleunigung
float airFrictionConstant, // 7. Luftreibungskonstante
float groundRepulsionConstant, // 8. Boden-Abstoss-Konstante
float groundFrictionConstant, // 9. Bodenreibungskonstante
float groundAbsorptionConstant, // 10. Boden-Absorptions-Konstante
float groundHeight // 11. Höhe des Bodens (Y Position)
) : Simulation(numOfMasses, m) // Die Super Klasse erzeugt Massen mit jeweils einem Gewicht von m
{
this->gravitation = gravitation;
this->airFrictionConstant = airFrictionConstant;
this->groundFrictionConstant = groundFrictionConstant;
this->groundRepulsionConstant = groundRepulsionConstant;
this->groundAbsorptionConstant = groundAbsorptionConstant;
this->groundHeight = groundHeight;
for (int a = 0; a < numOfMasses; ++a) // setze die Anfangspositionen der Massen, iteriere mit For(;;)
{
masses[a]->pos.x = a * springLength; // Setze X-Position der Masse[a] mit der Distanz springLength zu seinem Nachbarn
masses[a]->pos.y = 0; // Setze Y-Position auf 0, so dass sie horizontal steht
masses[a]->pos.z = 0; // Setze Z-Position auf 0, so dass es einfach aussieht
}
springs = new Spring*[numOfMasses - 1]; // erzeuge [numOfMasses - 1] Zeiger für die Federn
// ([numOfMasses - 1] Federn werden für numOfMasses Massen benötigt)
for (a = 0; a < numOfMasses - 1; ++a) // um jede zu erzeugen, durchlaufe eine Schleife
{
// erzeuge die Feder mit Index "a" zwischen der Masse mit Index "a" und einer anderen Masse mit Index "a + 1".
springs[a] = new Spring(masses[a], masses[a + 1],
springConstant, springLength, springFrictionConstant);
}
}
void solve() // solve() wird überschrieben, da wir Kräfte anzuwenden haben
{
for (int a = 0; a < numOfMasses - 1; ++a) // Wende Kraft auf alle Federn an
{
springs[a]->solve(); // Feder mit dem Index "a" soll seine Kraft anwenden
}
for (a = 0; a < numOfMasses; ++a) // Starte eine Schleife, um die Kräfte anzuwenden, die für alle Massen gleich sind
{
masses[a]->applyForce(gravitation * masses[a]->m); // Die Gravitationskraft
// Die Luftreibung
masses[a]->applyForce(-masses[a]->vel * airFrictionConstant);
if (masses[a]->pos.y < groundHeight) // Kräfte vom Boden werden angewandt, wenn eine Masse mit dem Boden kollidiert
{
Vector3D v; // Ein temporärer Vector3D
v = masses[a]->vel; // ermittle die Geschwindigkeit
v.y = 0; // Lasse die Geschwindigkeitskomponente in die Y-Richgung aus
// Die Geschwindigkeit in die Y-Richtung wird ausgelassen, da wir eine Reibungskraft erzeugen werden, um
// einen Schlitter-Effekt zu erzeugen. Das
Schlittern findet parallel zum Boden statt. Geschwindigkeit in die
Y-Richtung wird für den
// Absorption Effekt genutzt.
// Bodenreibungskraft wird angewandt
masses[a]->applyForce(-v * groundFrictionConstant);
v = masses[a]->vel; // ermittle die Geschwindigkeit
v.x = 0; // Lasse die X und Z Komponenten in der Geschwindigkeit aus
v.z = 0; // Wir werden v im Absorption Effekt nutzen
// Oben haben wir eine Geschwindigkeit ermittelt, die vertikal zum Boden ist und die in der
// Absorptionskraft verwendet wird
if (v.y < 0) // Absorbiere Energie nur dann, wenn eine Masse mit dem Boden kollidiert
// Die Absorptionskraft wird angewandt
masses[a]->applyForce(-v * groundAbsorptionConstant);
// Der Boden sollte die Masse wie eine Feder abstossen.
// Mit "Vector3D(0, groundRepulsionConstant, 0)" erzeugen wir einen Vektor in die Ebenen-Normalen-Richtung
// mit einer Länge von groundRepulsionConstant.
// mit (groundHeight - masses[a]->pos.y) stossen wir eine Masse so doll ab, wie sie auf dem Boden aufgekommen ist.
Vector3D force = Vector3D(0, groundRepulsionConstant, 0) *
(groundHeight - masses[a]->pos.y);
masses[a]->applyForce(force); // Die Boden-Abstossungs-Kraft wird angewandt
}
}
}
void simulate(float dt) // simulate(float dt) wird überschrieben, da wir simulieren wollen
// Die Bewegung von ropeConnectionPos
{
Simulation::simulate(dt); // Die Super Klasse soll die Massen simulieren
ropeConnectionPos += ropeConnectionVel * dt; // Iteriere die Positon von ropeConnectionPos
if (ropeConnectionPos.y < groundHeight) // ropeConnectionPos soll nicht unter den Boden gehen
{
ropeConnectionPos.y = groundHeight;
ropeConnectionVel.y = 0;
}
masses[0]->pos = ropeConnectionPos; // Die Masse mit dem Index "0" sollte an Postion ropeConnectionPos stehen
masses[0]->vel = ropeConnectionVel; // Die Geschwindigkeit der Masse wird gleich ropeConnectionVel gesetzt
}
void setRopeConnectionVel(Vector3D ropeConnectionVel) // Die Methode um ropeConnectionVel einen Wert zuzuweisen
{
this->ropeConnectionVel = ropeConnectionVel;
}
RopeSimulation* ropeSimulation = new RopeSimulation( 80, // 80 Partikel (Massen) 0.05f, // Jeder Partikel hat ein Gewicht von 50 Gramm 10000.0f, // Feder Konstante in dem Seil 0.05f, // Normale Länge der Federn in dem Seil 0.2f, // Feder innere Reibungs-Konstante Vector3D(0, -9.81f, 0), // Gravitationsbeschleunigung 0.02f, // Luftreibungskonstante 100.0f, // Boden-Abstoss-Konstante 0.2f, // Boden-Gleitreibungs-Konstante 2.0f, // Boden-Apsorptions-Konstante -1.5f); // Höhe des Bodens
float dt = milliseconds / 1000.0f; // konvertiert Millisekunde in Sekunden float maxPossible_dt = 0.002f; // Maxmal mögliches dt ist gleich 0.002 Sekunden // Dies wird benötigt, um zu verhindern, dass ein nicht-präziser dt Wert übergeben wird int numOfIterations = (int)(dt / maxPossible_dt) + 1; // berechne Anzahl der Iterationen, die während dieser Aktualisierung gemacht werden, abhängig von maxPossible_dt und dt if (numOfIterations != 0) // verhindere Division durch null dt = dt / numOfIterations; // dt sollte entsprechend numOfIterations aktualisiert werden for (int a = 0; a < numOfIterations; ++a) // wir müssen die Simulation "numOfIterations" mal iterieren ropeSimulation->operate(dt);